02. 列表、字典和元组

本章概要

  • 学习材料:一个有序持仓列表、证券信息字典和固定配置元组。
  • 本章任务:创建、访问和修改三种结构,并按“是否有序、是否按键查找、是否允许修改”选择类型。
  • 完成后你将得到:列表追加结果、字典键值查询、元组切片和一次错误类型选择的纠正。
  • 自我检查:逐项核对索引/键与可变性;尚未掌握循环时可用直接访问完成核心检查。
  • 拓展练习:把同一三问选择规则拓展应用到简单行业持仓清单。

零 Python 先修桥接|循环与条件

本章会在正式控制流章节前少量使用 for/if/enumerate。先掌握最小定义:for 逐项取值,if 只在条件为真时执行,enumerate 同时给出序号与元素;复杂分支和循环设计仍在第8—9章系统学习。

codes=['A','B']  # 创建两个中性代码供低风险练习
for position,code in enumerate(codes,start=1):  # 逐项取得从1开始的序号与代码
    if code=='A':  # 检查当前代码是否为A
        print(position,code)  # 只输出满足条件的序号与代码

即时练习:先预测输出,再把条件改为 code=='B';若不能逐轮写出 position/code,先完成本页再进入列表实战。

零 Python 先修桥接|推导式

推导式 [表达式 for 元素 in 序列 if 条件] 是“循环取值+条件保留”的紧凑写法;本章只要求能把它还原成普通循环,第9章再讨论复杂写法。

even_values=[]  # 初始化普通循环的结果列表
for value in [1,2,3,4]:  # 逐项检查确定性整数序列
    if value%2==0:  # 判断当前值是否为偶数
        even_values.append(value)  # 保存满足条件的偶数
same_values=[value for value in [1,2,3,4] if value%2==0]  # 用推导式表达同一规则
assert even_values==same_values==[2,4]  # 核验两种写法得到一致结果

即时反馈:输出应为 [2,4];若推导式顺序不清楚,作答时可以使用普通循环,不要求在第2章独立编写复杂推导式。

本章概览:Python三大复合数据类型

在金融数据分析中,我们经常需要处理成组的相关数据

Python提供了三种核心复合数据类型:

类型 特点 典型应用
列表 (List) 可变、有序 股价时间序列
字典 (Dictionary) 键值对、快速查找 股票信息存储
元组 (Tuple) 外层槽位固定;成员另行管理 固定记录结构

数据结构的性能特征

从计算机科学角度看,三种数据类型代表了不同的抽象层次:

  • 列表:动态数组,O(1)随机访问,O(n)插入/删除
  • 字典:哈希表,平均 O(1) 查找、插入和删除;最坏情况可退化
  • 元组:槽位不可变;仅当所有元素(包括嵌套成员)均可哈希时才可作为字典键

数据结构性能对比图

三种数据结构性能对比 列表、字典、元组的性能特征对比图 列表 (List) 有序、可变 O(1) 按索引访问 O(n) 按值查找 时间序列 [0, 1, 2] 字典 (Dict) 键值映射 平均 O(1) 查找 键必须可哈希 股票信息 元组 (Tuple) 有序、槽位固定 成员可哈希才可作键 槽位不可原地重绑 固定配置 (0, 1, 2)

第一部分:列表 (List)

列表是Python中最常用的数据结构之一:

  • 可变的 (Mutable):可以增删改元素
  • 有序的 (Ordered):元素按插入顺序排列
  • 异构的:可以包含不同类型的元素

在金融应用中,列表常用于存储:

  • 时间序列数据(日期、价格序列)
  • 投资组合成分(股票代码列表)
  • 交易记录集合

运行前预测|列表的创建——混合类型列表

  • 输入预测:运行前先写出 x 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到x 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“列表的创建——混合类型列表”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 列表的创建——混合类型列表

# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#列表应用
x=['finance','risk management','金融风险管理',8.88]
print(x)  # 输出混合类型列表的内容(含字符串、数值等多种数据类型)

任务复盘|列表的创建——混合类型列表

运行后核对:核对 x 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

列表的异构性与内存模型

列表可以包含不同类型的元素(这是Python列表的独特优势):

  • 字符串 'finance'、数值 8.88 可以共存
  • 列表存储的是对象的引用而非对象本身

关键时间复杂度

列表操作复杂度速查

操作 复杂度 说明
x[i] 索引访问 O(1) 直接定位
x[a:b] 切片 O(k) k为切片长度
len(x) O(1) 内置存储长度
x.append() 均摊O(1) 动态数组扩容

实战:用列表管理投资组合

Listing 1: 创建和操作投资组合列表
展开投资组合列表示例
# 创建初始投资组合(存储A股股票代码)
portfolio = ['600519.SH', '000858.SZ', '600036.SH', '000002.SZ']
# 6位数字.SH(上海)或.SZ(深圳)

# 添加新股票(末尾追加,均摊O(1))
portfolio.append('601318.SH')  # 中国平安

# 移除股票(需遍历查找,O(n))
portfolio.remove('000002.SZ')  # 万科A

# 输出当前持仓
print(f'当前持仓股票数量: {len(portfolio)}')
for i, stock in enumerate(portfolio, start=1):
    print(f'  {i}. {stock}')
当前持仓股票数量: 4
  1. 600519.SH
  2. 000858.SZ
  3. 600036.SH
  4. 601318.SH

列表常用方法速查

方法 功能 示例
append(x) 末尾添加元素 lst.append('new')
remove(x) 删除第一个匹配 lst.remove('old')
insert(i,x) 指定位置插入 lst.insert(0,'first')
pop(i) 删除并返回元素 lst.pop(-1)

列表常用方法:排序与查找

方法 功能 示例
sort() 原地排序 lst.sort()
reverse() 原地反转 lst.reverse()
index(x) 查找元素位置 lst.index('target')

注意appendsortreverse 都是原地修改,返回 None

第二部分:字典 (Dictionary)

字典是Python中最重要的数据结构之一:

  • 可变的:可以增删改键值对
  • Python 3.7+ 保持插入顺序
  • 基于哈希表实现,查找速度极快

字典的核心特点是键值对 (Key-Value Pair) 结构:

  • 键 (Key):必须可哈希;元组仅在其全部元素都可哈希时才满足
  • 值 (Value):可以是任意类型

运行前预测|字典的创建——沪深300指数信息

  • 输入预测:运行前先写出 dict1 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到dict1 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“字典的创建——沪深300指数信息”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 字典的创建——沪深300指数信息

# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#字典应用
dict1={'指数名称':'沪深300',
      '证券代码':'000300',  # 沪深300指数的证券代码
      '交易日期':'2022-09-14',  # 交易日期
      '涨跌幅':0.42  # 当日涨跌幅(百分比)
    }  # 创建沪深300指数信息字典
print(dict1)  # 输出沪深300指数信息字典

任务复盘|字典的创建——沪深300指数信息

运行后核对:核对 dict1 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

字典底层原理:哈希表

字典底层使用哈希表 (Hash Table) 实现:

  1. 通过 hash(key) 计算哈希值
  2. 哈希值决定存储位置(桶)
  3. 哈希冲突通过开放寻址法解决

哈希表性能对比

操作 字典 列表
查找元素 平均 O(1) O(n)
插入元素 平均/摊销 O(1) 末尾追加摊销 O(1),中间插入 O(n)
删除元素 平均 O(1) 末尾弹出 O(1),按位置移动 O(n)
遍历全部 O(n) O(n)

键的要求:必须可哈希intstr 通常可哈希;元组仅在所有元素均可哈希时可作键,listdict 不可作键。

实战:字典存储股票信息

Listing 2: 使用字典管理股票信息
展开股票信息字典示例
# 创建股票信息字典
stock_info = {
    'code': '600519.SH',
    'name': '贵州茅台',
    'price': 1850.00,
    'volume': 2500000
}

# 更新股价
stock_info['price'] = 1860.00

# 添加新字段(市盈率)
stock_info['pe_ratio'] = 45.5

# 检查字段是否存在
if 'market_cap' in stock_info:
    print(f'市值: {stock_info["market_cap"]}')
else:
    print('市值信息缺失')

# 批量更新
stock_info.update({'price': 1870.00, 'volume': 2600000})
print(stock_info)
市值信息缺失
{'code': '600519.SH', 'name': '贵州茅台', 'price': 1870.0, 'volume': 2600000, 'pe_ratio': 45.5}

字典常用方法速查

方法 功能 示例
d[key] 获取值(不存在报错) d['name']
d.get(key, default) 安全获取值 d.get('name', '未知')
d[key] = val 设置/更新值 d['price'] = 100
d.update(other) 批量更新 d.update({...})

字典常用方法:检查与遍历

方法 功能 示例
key in d 检查键是否存在 'name' in d
d.keys() 获取所有键 返回视图对象
d.values() 获取所有值 返回视图对象
d.items() 获取所有键值对 返回视图对象

字典视图对象:动态反映变化

keys()values()items() 返回的是视图对象

Listing 3: 字典视图对象的集合操作
展开字典视图集合运算示例
# 两个字典
dict_a = {'a': 1, 'b': 2, 'c': 3}
dict_b = {'b': 2, 'c': 3, 'd': 4}

# 键的交集(两个字典共有的键)
common_keys = dict_a.keys() & dict_b.keys()
print(f'共有键: {common_keys}')  # {'b', 'c'}

# 键的差集(A有B没有的键)
unique_keys = dict_a.keys() - dict_b.keys()
print(f'A独有键: {unique_keys}')  # {'a'}
共有键: {'c', 'b'}
A独有键: {'a'}

视图对象支持集合运算:&(交集)、|(并集)、-(差集)

第三部分:元组 (Tuple)

元组是槽位不可变的有序序列;若槽位引用列表等可变对象,对象内部内容仍可变化。

不可变性带来的优势:

  • 并发边界:槽位不可改不等于复合操作线程安全,共享可变对象仍可能需要锁
  • 条件可哈希:自身及嵌套成员均可哈希,方可作键或集合元素
  • 性能优化:Python可以进行内存优化
  • 误改边界:仅防外层槽位误重绑;内层完整性另靠校验、授权、版本和所有权

运行前预测|元组的创建与切片操作

  • 输入预测:运行前先写出 tup 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到tup 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“元组的创建与切片操作”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 元组的创建与切片操作

# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#元组应用
tup=('finance','风险管理',2022,88.88)
print(tup[1:3])  # 输出元组切片(索引1到2的元素:'风险管理'和2022)

任务复盘|元组的创建与切片操作

运行后核对:核对 tup 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

切片规则 tup[start:end]

  • 包含 start 索引,不包含 end 索引
  • tup[1:3] → 索引1和索引2的元素

选学扩展(第8—9章后):命名元组

标准元组只能通过索引访问,代码可读性差。命名元组解决了这个问题:

Listing 4: 命名元组在金融数据中的应用
展开命名元组示例
from collections import namedtuple

# 定义命名元组类型
Stock = namedtuple('Stock', ['code', 'name', 'price', 'volume'])

# 创建实例
stock = Stock('600519.SH', '贵州茅台', 1850.00, 2500000)

# 通过字段名访问(更直观)
print(f'股票名称: {stock.name}')
print(f'股票价格: {stock.price}')

# 仍然支持索引访问
print(f'股票代码: {stock[0]}')
股票名称: 贵州茅台
股票价格: 1850.0
股票代码: 600519.SH

数据类型选择指南

根据使用场景选择合适的数据类型:

场景 推荐类型 理由
时间序列数据 列表 有序、可动态增删
键值对数据 字典 O(1)快速查找
固定记录结构 元组 外层槽位固定;校验与权限另管
去重数据 集合 自动去重
矩阵运算 NumPy数组 高性能数值计算

数据类型选择三问

  • 需要修改?→ 列表或字典
  • 需要快速查找?→ 字典
  • 外层槽位需固定且成员管理明确?→ 元组

性能实测:列表 vs 字典查找

Listing 5: 列表与字典的查找性能比较
展开查找性能实测代码
# 导入重复计时与稳健汇总工具以避免单次观测误导。
import statistics,timeit

element_count=1_000_000  # 固定两种结构包含相同数量的键。
test_list=list(range(element_count))  # 建立需要线性扫描的列表。
test_dict={key:None for key in range(element_count)}  # 建立按哈希定位的字典。
target_key=element_count-1  # 选择列表末端键以展示线性扫描负载。
repeat_count=7  # 用七轮重复观察运行环境波动。
lookups_per_repeat=10  # 每轮对两种结构执行相同次数的查询。
timer_context={'target_key':target_key,'test_list':test_list,'test_dict':test_dict}  # 共享同一计时上下文。
list_samples=timeit.repeat('target_key in test_list',globals=timer_context,repeat=repeat_count,number=lookups_per_repeat)  # 重复测量列表查询。
dict_samples=timeit.repeat('target_key in test_dict',globals=timer_context,repeat=repeat_count,number=lookups_per_repeat)  # 重复测量字典查询。
list_per_lookup=[sample/lookups_per_repeat for sample in list_samples]  # 换算列表单次查询耗时。
dict_per_lookup=[sample/lookups_per_repeat for sample in dict_samples]  # 换算字典单次查询耗时。
ratio_samples=[list_value/dict_value for list_value,dict_value in zip(list_per_lookup,dict_per_lookup)]  # 逐轮计算同环境耗时比。
print(f'列表单次中位数={statistics.median(list_per_lookup):.6f}秒,范围=[{min(list_per_lookup):.6f}, {max(list_per_lookup):.6f}]')  # 报告列表稳健汇总。
print(f'字典单次中位数={statistics.median(dict_per_lookup):.9f}秒,范围=[{min(dict_per_lookup):.9f}, {max(dict_per_lookup):.9f}]')  # 报告字典稳健汇总。
print(f'同轮耗时比中位数={statistics.median(ratio_samples):.1f}倍,范围=[{min(ratio_samples):.1f}, {max(ratio_samples):.1f}]')  # 报告波动而非固定倍数。
列表单次中位数=0.003882秒,范围=[0.003872, 0.003888]
字典单次中位数=0.000000046秒,范围=[0.000000045, 0.000000249]
同轮耗时比中位数=84441.5倍,范围=[15602.4, 86440.4]

解释边界:列表成员查询为 \(O(n)\);字典成员查询平均为 \(O(1)\),但建表成本、内存、哈希冲突与实际查询组合也要计入决策。

深拷贝与浅拷贝:避免数据污染

浅拷贝与深拷贝对比 展示浅拷贝共享内层引用、深拷贝完全独立的区别 浅拷贝 vs 深拷贝 浅拷贝 (Shallow Copy) 原始列表 浅拷贝列表 共享内层对象 修改原始 → 拷贝也变 深拷贝 (Deep Copy) 原始列表 深拷贝列表 内层对象 A 内层对象 B 完全独立,互不影响

拷贝机制代码演示

Listing 6: 浅拷贝与深拷贝的区别演示
展开深浅拷贝演示代码
import copy

# 嵌套列表(模拟投资组合持仓)
portfolio = [
    ['600519.SH', 100, 1850.00],
    ['000858.SZ', 200, 85.50]
]

# 浅拷贝:内层仍然共享引用
portfolio_shallow = portfolio.copy()

# 深拷贝:完全独立的副本
portfolio_deep = copy.deepcopy(portfolio)

# 修改原始数据
portfolio[0][2] = 1900.00

# 浅拷贝受影响(共享内层引用)
print(f'浅拷贝: {portfolio_shallow[0][2]}')  # 1900.00

# 深拷贝不受影响(完全独立)
print(f'深拷贝: {portfolio_deep[0][2]}')  # 1850.00
浅拷贝: 1900.0
深拷贝: 1850.0

拷贝最佳实践

场景 方法 说明
简单列表(不含嵌套) list.copy()[:] 浅拷贝即可
嵌套结构且须隔离可变成员 copy.deepcopy() 先确认成员可复制、成本可接受
简单字典 dict.copy() 浅拷贝
嵌套字典且须隔离可变成员 copy.deepcopy() 若约定只读或共享可用浅拷贝

记住:先明确谁拥有并可修改内层对象;只有需要递归隔离且已评估性能、资源句柄与自定义对象语义时才使用深拷贝。

本章总结

三大数据类型核心特征

  • 列表:可变有序序列 → [] 创建
    • 适合:动态数据集合、时间序列
  • 字典:键值对映射 → {} 创建
    • 适合:结构化数据、快速查找
  • 元组:外层槽位不可重绑的有序序列 → () 创建
    • 适合:固定记录结构;完整性与安全仍需校验、授权、版本和所有权纪律

本章总结:选择与边界

  • 查找操作多时选字典(O(1))
  • 外层槽位需固定且成员管理明确时选元组
  • 嵌套结构拷贝用 copy.deepcopy()

随堂练习

  • 问题 1|需要准备哪些数据?:有序持仓列表、按代码查询的字典、外层槽位固定的配置元组。
  • 问题 2|需要完成哪些操作?:分别完成创建、访问、列表/字典修改与元组切片,再用三问选择结构。
  • 问题 3|应得到哪些结果?:追加后的列表、按键查询值、元组切片和结构选择理由。
  • 问题 4|怎样确认结果可靠?:核对索引/键是否存在与元组外层槽位不可重绑;性能、哈希、namedtuple 和深浅拷贝均为第8—9章后的选学,不决定完成本章。
  • 问题 5|换一个情境,怎样继续应用?:把三问选择规则拓展应用到简单行业持仓清单。
  • 作答提示:请依次写清所用数据、分析过程、所得结果、核对方法和拓展思考。课程所需数据见前言中的下载入口;教学平台固定题按页面说明完成。

教师参考解答|答案与说明 1

  • 所用数据与字段:有序持仓列表、按代码查询字典、外层槽位固定的配置元组。
  • 核心答案:列表用于保持顺序并追加,字典用于按证券代码查询,元组用于固定记录的外层槽位;元组成员的可变性和完整性控制须另行说明,性能与拷贝代码是选学依据,不进入核心内容。

教师参考解答|代码 1

展开代码(代码区可独立滚动)
holding_list=[('002415.SZ',.40),('600276.SH',.35),('600104.SH',.25)]  # 建立有序持仓列表
holding_list.append(('002230.SZ',.10))  # 用追加记录新增证券。
holding_list[1]=('600276.SH',.25)  # 用索引更新已有证券权重。
holding_dict=dict(holding_list)  # 建立证券代码到权重的查询映射。
holding_dict['600104.SH']=.20  # 用键更新已有证券权重。
query_code='600276.SH'  # 指定需要核对的证券代码。
query_weight=holding_dict[query_code]  # 按键读取该证券权重。
configuration=('季度再平衡','CNY','长三角')  # 建立外层槽位固定的配置元组。
configuration_slice=configuration[:2]  # 切片提取频率与币种且不改原元组。
structure_choices={'需要修改':'列表或字典','需要按键查找':'字典','外层槽位固定':'元组'}  # 回答结构选择三问。
assert query_weight==.25 and configuration==('季度再平衡','CNY','长三角')  # 核对查询值与原元组。
print({'list':holding_list,'lookup':query_weight,'tuple_slice':configuration_slice})  # 输出列表、查询与切片核心结果。
print({'three_questions':structure_choices})  # 输出三问选择及其理由线索。

教师参考解答|答案与说明 2

  • 拓展应用答案|行业持仓清单

教师参考解答|代码 2

展开代码(代码区可独立滚动)
industry_holding_list=[('002415.SZ',.60)]  # 建立电子行业有序持仓清单。
industry_holding_list.append(('002230.SZ',.40))  # 追加同一行业证券。
industry_weight_by_code=dict(industry_holding_list)  # 转成按代码查询的行业映射。
industry_query=industry_weight_by_code['002230.SZ']  # 查询新增证券权重。
assert len(industry_holding_list)==2 and industry_query==.40  # 核对追加与按键查询结果。
print({'industry_positions':industry_holding_list,'lookup':industry_query})  # 输出可新案例的行业持仓依据。

教师参考解答|答案与说明 3

  • 参考结果:追加并更新后的列表、字典更新后的按键查询值、元组切片、三问选择答案及行业持仓拓展应用结果。
  • 提示:索引或键不存在、切片边界不符或结构选择理由与需求冲突时只写出诊断。性能、哈希、NamedTuple 与深浅拷贝均为不计分选学。